Skip to content

refactor: migrate avoid_using_api#304

Open
solid-illiaaihistov wants to merge 8 commits into
solid-software:analysis_server_migrationfrom
solid-illiaaihistov:249-migrate-avoid_using_api
Open

refactor: migrate avoid_using_api#304
solid-illiaaihistov wants to merge 8 commits into
solid-software:analysis_server_migrationfrom
solid-illiaaihistov:249-migrate-avoid_using_api

Conversation

@solid-illiaaihistov

Copy link
Copy Markdown
Collaborator

Closes #249

@solid-illiaaihistov solid-illiaaihistov linked an issue Jun 30, 2026 that may be closed by this pull request

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the avoid_using_api lint rule to utilize the analyzer-based RuleContext and RuleVisitorRegistry instead of custom_lint APIs. It replaces AvoidUsingApiLinter with AvoidUsingApiVisitor (extending SimpleAstVisitor), introduces several AST node helper extensions in node_utils.dart, refactors path matching in path_utils.dart, and replaces old integration tests with a comprehensive unit test suite. The review feedback suggests making the rootPath parameter in _getActiveEntries nullable to align with shouldSkipFile and passing context.package?.root.path directly instead of coalescing it to an empty string.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart
Comment thread lib/src/lints/avoid_using_api/visitors/avoid_using_api_visitor.dart
@solid-illiaaihistov

Copy link
Copy Markdown
Collaborator Author

I found a bug related to the use of severity in this rule, and I'm working on a fix.

}).toList();
}

List<AvoidUsingApiEntryParameters>? _cachedActiveEntries;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be before constructor

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Comment on lines +242 to +245
final element = node.element;
if (element is LocalFunctionElement ||
element is TopLevelFunctionElement ||
element is PropertyAccessorElement) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
final element = node.element;
if (element is LocalFunctionElement ||
element is TopLevelFunctionElement ||
element is PropertyAccessorElement) {
if (node.element
case LocalFunctionElement() ||
TopLevelFunctionElement() ||
PropertyAccessorElement()) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Comment on lines +256 to +275
if (source == null) return;

final className = entry.className;
final identifier = entry.identifier;

switch ((className, identifier)) {
case (null, null):
// banSource
if (matchesSource(node.sourceUrl, source)) {
reporter.atNode(node, _getLintCode(entry));
}
case (final String className, null):
// banClassFromSource
if (node.name.lexeme == className &&
matchesSource(node.sourceUrl, source)) {
reporter.atNode(node, _getLintCode(entry));
}
default:
break;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (source == null) return;
final className = entry.className;
final identifier = entry.identifier;
switch ((className, identifier)) {
case (null, null):
// banSource
if (matchesSource(node.sourceUrl, source)) {
reporter.atNode(node, _getLintCode(entry));
}
case (final String className, null):
// banClassFromSource
if (node.name.lexeme == className &&
matchesSource(node.sourceUrl, source)) {
reporter.atNode(node, _getLintCode(entry));
}
default:
break;
}
if (source == null ||
entry.identifier != null ||
entry.className == null ||
!matchesSource(node.sourceUrl, source)) {
return;
}
reporter.atNode(node, _getLintCode(entry));

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Comment on lines +283 to +302
final source = entry.source;
if (source == null) return;

final className = entry.className;

switch ((className, entry.identifier)) {
case (final String className, null):
// banClassFromSource
final typeElement = node.declaredType?.element;
if (typeElement?.name == className &&
matchesSource(typeElement?.libraryUri, source)) {
reporter.atOffset(
offset: node.name.offset,
length: node.name.length,
diagnosticCode: _getLintCode(entry),
);
}
default:
break;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
final source = entry.source;
if (source == null) return;
final className = entry.className;
switch ((className, entry.identifier)) {
case (final String className, null):
// banClassFromSource
final typeElement = node.declaredType?.element;
if (typeElement?.name == className &&
matchesSource(typeElement?.libraryUri, source)) {
reporter.atOffset(
offset: node.name.offset,
length: node.name.length,
diagnosticCode: _getLintCode(entry),
);
}
default:
break;
}
final source = entry.source;
final className = entry.className;
final typeElement = node.declaredType?.element;
if (source == null ||
entry.identifier != null ||
className == null ||
typeElement?.name != className ||
!matchesSource(typeElement?.libraryUri, source)) {
return;
}
reporter.atOffset(
offset: node.name.offset,
length: node.name.length,
diagnosticCode: _getLintCode(entry),
);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Comment on lines +330 to +345
if (actualClassName != className) {
return;
}

if (node.constructorName.name?.name != expectedConstructorName) {
return;
}

if (!node.argumentList.containsNamed(namedParameter)) {
return;
}

if (matchesSource(
node.constructorName.type.element?.libraryUri,
source,
)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (actualClassName != className) {
return;
}
if (node.constructorName.name?.name != expectedConstructorName) {
return;
}
if (!node.argumentList.containsNamed(namedParameter)) {
return;
}
if (matchesSource(
node.constructorName.type.element?.libraryUri,
source,
)) {
if (actualClassName == className &&
node.constructorName.name?.name == expectedConstructorName &&
node.argumentList.containsNamed(namedParameter) &&
matchesSource(
node.constructorName.type.element?.libraryUri,
source,
)) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Comment on lines +403 to +415
if (element == null) return false;

final target = element is InterfaceElement
? element
: element.enclosingElement;
if (target == null) return false;

if (target is InterfaceElement || target is ExtensionElement) {
return target.name == className &&
matchesSource(target.libraryUri, source);
}

return false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (element == null) return false;
final target = element is InterfaceElement
? element
: element.enclosingElement;
if (target == null) return false;
if (target is InterfaceElement || target is ExtensionElement) {
return target.name == className &&
matchesSource(target.libraryUri, source);
}
return false;
final target = element is InterfaceElement
? element
: element?.enclosingElement;
if (target case InterfaceElement() || ExtensionElement()
when target != null) {
return target.name == className &&
matchesSource(target.libraryUri, source);
}
return false;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are probably more similar refactors possible in this file that I missed

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I implemented similar improvements in other parts of this class as well.

}

@override
void visitVariableDeclaration(VariableDeclaration node) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same is applicable to visitNamedType, visitSimpleIdentifier, visitInstanceCreationExpression, visitMethodInvocation

  @override
  void visitVariableDeclaration(VariableDeclaration node) =>
      _visit(node, super.visitVariableDeclaration, _checkVariableDeclaration);

  void _visit<T>(
    T node,
    void Function(T) visitSuper,
    void Function(
      T,
      AvoidUsingApiEntryParameters,
      DiagnosticReporter,
    )
    visitEntry,
  ) {
    visitSuper(node);

    final resolved = _resolveContext();
    if (resolved == null) return;
    final (:activeEntries, :reporter) = resolved;

    for (final entry in activeEntries) {
      visitEntry(node, entry, reporter);
    }
  }

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!
Thank you!

}
}

bool _isMemberOrClass(Element? element, String className, String source) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like it can be extracted to util?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Comment on lines +336 to +352
final source = entry.source;
final className = entry.className;
final namedParameter = entry.namedParameter;
if (source == null ||
className == null ||
namedParameter == null ||
node.methodName.name != entry.identifier) {
return;
}

final enclosingElement = node.methodName.element?.enclosingElement;
if (enclosingElement != null &&
enclosingElement.name == className &&
node.argumentList.containsNamed(namedParameter) &&
matchesSource(enclosingElement.libraryUri, source)) {
reporter.atNode(node.methodName, _getLintCode(entry));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
final source = entry.source;
final className = entry.className;
final namedParameter = entry.namedParameter;
if (source == null ||
className == null ||
namedParameter == null ||
node.methodName.name != entry.identifier) {
return;
}
final enclosingElement = node.methodName.element?.enclosingElement;
if (enclosingElement != null &&
enclosingElement.name == className &&
node.argumentList.containsNamed(namedParameter) &&
matchesSource(enclosingElement.libraryUri, source)) {
reporter.atNode(node.methodName, _getLintCode(entry));
}
final AvoidUsingApiEntryParameters(:source, :className, :namedParameter) =
entry;
final methodName = node.methodName;
final enclosingElement = methodName.element?.enclosingElement;
if (source == null ||
className == null ||
namedParameter == null ||
methodName.name != entry.identifier ||
enclosingElement == null ||
enclosingElement.name != className ||
!node.argumentList.containsNamed(namedParameter) ||
!matchesSource(enclosingElement.libraryUri, source)) {
return;
}
reporter.atNode(methodName, _getLintCode(entry));

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

return;
}

reporter.atNode(node, _getLintCode(entry));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is repeated 8 times, why don't we wrap reporter into a function when we return it from _resolveContext so it looks more like:

Suggested change
reporter.atNode(node, _getLintCode(entry));
report(node);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

…ied traversal method and moving member checks to extensions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate avoid_using_api

2 participants